package gwtappcontainer.server.apps.persistentset;

import static gwtappcontainer.server.apps.persistentset.OfyService.ofy;
import gwtappcontainer.server.DatastoreNamespaces;
import gwtappcontainer.server.apis.APIException;
import gwtappcontainer.server.apis.security.GateKeeper;
import gwtappcontainer.shared.apis.APIResponse.Status;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.TreeSet;

import com.google.appengine.api.NamespaceManager;
import com.googlecode.objectify.Key;
import com.googlecode.objectify.Ref;

class PersistentSetRepository {
	
	private final static String NAMESPACE = DatastoreNamespaces.PERSISTENTSET;
	private final static int MAX_ELEMENTS_IN_SET = 50000;
	
	static long addSet(String login) {
		GateKeeper.ensureValidUser(login);
		NamespaceManager.set(NAMESPACE);
				
		SetCollectionEntity entity = new SetCollectionEntity();
						
		ofy().save().entity(entity).now();
		
		return entity.id;
	}
	
	static void deleteSet(long bigSetId, String login) {
		GateKeeper.ensureValidUser(login);
		
		NamespaceManager.set(NAMESPACE);		
		SetCollectionEntity bigSetEntity = ofy().load()
				.type(SetCollectionEntity.class).id(bigSetId).get();
		
		if (null == bigSetEntity)
			throw new APIException(Status.ERROR_RESOURCE_DOES_NOT_EXIST, 
					"there is no big set with id [" + bigSetId + "]");
				
		Map<Key<SetEntity>, SetEntity>  all = ofy().load().values(bigSetEntity.sets);
		
		ofy().delete().keys(all.keySet()).now();
		ofy().delete().entity(bigSetEntity).now();		
	}
	
	static TreeSet<Long> getAllSets() {
		NamespaceManager.set(NAMESPACE);
		List<SetCollectionEntity> all = ofy().load().type(SetCollectionEntity.class).list();
		
		TreeSet<Long> ids = new TreeSet<Long>();
		for (SetCollectionEntity element : all) {
			ids.add(element.id);
		}
		
		return ids;
	}
	
	static void addElementToSet(long bigSetId, long element, String login) {
		HashSet<Long> elements = new HashSet<Long>();
		elements.add(element);
		addElementsToSet(bigSetId, elements, login);
	}
	
	static void addElementsToSet(long bigSetId, HashSet<Long> elements, String login) {
		GateKeeper.ensureValidUser(login);
		
		HashSet<Long> existing = getAllElements(bigSetId);
		
		elements.removeAll(existing);
		
		if (elements.size() == 0)
			return;
						
		//cannot add more than 1000 elements
		//this restriction is necessary to ensure elements never 
		//need to get added to multiple underlying set entities
		//this is a restriction just to keep the code simple
		
		if (elements.size() > 1000) 
			throw new APIException(Status.ERROR_RESOURCE_INCORRECTLY_SPECIFIED, 
					"Cannot add more than 1000 new elements in one shot to a persistent set. Attempted to add " + 
							elements.size() + " new elements");
		
		NamespaceManager.set(NAMESPACE);
		SetCollectionEntity bigSetEntity = ofy().load()
				.type(SetCollectionEntity.class).id(bigSetId).get();
		
		if (null == bigSetEntity)
			throw new APIException(Status.ERROR_RESOURCE_DOES_NOT_EXIST, 
					"there is no big set with id [" + bigSetId + "]");
		
		if (bigSetEntity.sets.size() == 0) {			
			//no existing elements
			SetEntity setEntity = new SetEntity();			
			setEntity.elements.addAll(elements);
			
			ofy().save().entity(setEntity).now();			
			
			bigSetEntity.sets.add(Ref.create(setEntity));											
			ofy().save().entity(bigSetEntity).now();
			return;
		}
		
		int size = bigSetEntity.sets.size();
		SetEntity setEntity = bigSetEntity.sets.get(size - 1).get();
		
		if (setEntity.elements.size() < MAX_ELEMENTS_IN_SET) {			
			setEntity.elements.addAll(elements);
			
			ofy().save().entity(setEntity).now();
			return;
		}
		
		setEntity = new SetEntity();
		setEntity.elements.addAll(elements);
		ofy().save().entity(setEntity).now();
		bigSetEntity.sets.add(Ref.create(setEntity));
		ofy().save().entity(bigSetEntity).now();
		
		return;
	}
	
	static HashSet<Long> getAllElements(long bigSetId) {
		NamespaceManager.set(NAMESPACE);
		
		SetCollectionEntity bigSetEntity = ofy().load()
				.type(SetCollectionEntity.class).id(bigSetId).get();
		
		if (null == bigSetEntity)
			throw new APIException(Status.ERROR_RESOURCE_DOES_NOT_EXIST, 
					"there is no big set with id [" + bigSetId + "]");
		
		Map<Key<SetEntity>, SetEntity>  all = ofy().load().values(bigSetEntity.sets);
		
		HashSet<Long> universalSet = new HashSet<Long>();
		
		for (SetEntity setEntity : all.values()) {
			universalSet.addAll(setEntity.elements);
		}
						
		return universalSet;
	}
	
	static void deleteElementsFromSet(long bigSetId, HashSet<Long> elements, String login) {
		HashSet<Long> existing = getAllElements(bigSetId);
		
		elements.retainAll(existing);
		if (elements.size() == 0)
			return;
		
		NamespaceManager.set(NAMESPACE);
		SetCollectionEntity bigSetEntity = ofy().load()
				.type(SetCollectionEntity.class).id(bigSetId).get();
		
		Map<Key<SetEntity>, SetEntity>  all = ofy().load().values(bigSetEntity.sets);
				
		List<SetEntity> changedEntities = new ArrayList<SetEntity>();
		for (SetEntity setEntity : all.values()) {
			boolean change = setEntity.elements.removeAll(elements);
			if (change)
				changedEntities.add(setEntity);
		}
		ofy().save().entities(changedEntities).now();
	}
	
	static void deleteElementFromSet(long bigSetId, long element, String login) {
		
		HashSet<Long> elements = new HashSet<Long>();
		elements.add(element);
		
		deleteElementsFromSet(bigSetId, elements, login);		
	}
}
